import json
import time
import threading
import os.path
import io
import socket

#tips：距离向量指的是该路由器到局域网中其他路由器的代价
#距离向量表指的是局域网内所有路由器距离向量的集合，在本程序中变量名为DV

TOPO_FILE="topo.json"
INFINITY=999999
LISTEN_DV_PORT=6666#监听DV交换的端口
LISTEN_MG_PORT=8888#监听消息的端口
SOCKET_BUFFER_SIZE=1024

#当前局域网内路由器及其IP
NEIGHBOR_ROUTER={'A':'192.168.56.1','B':'192.168.56.4','C':'192.168.56.7','E':'192.168.56.6'}
FORWARD_TABLE={}#转发表

RouterName='A'#当前路由器名称
DV={}#距离向量表
neighbor_cost={}#到各邻居路由器的代价

def init():#初始化
    #读取拓扑图文件（topo.json）
    f=open(TOPO_FILE,'r')
    size=os.path.getsize(TOPO_FILE)
    s=f.read(size)
    f.close()
    j=json.loads(s)

    global neighbor_cost,DV
    
    #获取到邻居路由器的代价
    neighbor_cost=j.get(RouterName,{})

    if len(neighbor_cost)==0:
        return False
    
    neighbor_cost[RouterName]=0#到自身的代价为0
    for k in neighbor_cost.keys():
        if k==RouterName:#该路由器到邻居的代价
            DV[k]=neighbor_cost.copy()
        else:
            DV[k]={}#局域网中其他路由器的距离向量，初始化为正无穷
            for k1 in neighbor_cost.keys():
                DV[k][k1]=INFINITY
    print("Initialization complete!")
    
    #输出初始化的结果
    for k in DV.keys():
        print(k,end='\t')
        for k1,v1 in DV[k].items():
            if v1==INFINITY:
                print('{}:∞'.format(k1),end=' ')
            else:
                print('{}:{}'.format(k1,v1), end= ' ')
        print()
    return True

def send_DV_neighbor():#发送更新后的距离向量给所有邻居
    for neighbor in neighbor_cost.keys():
        if neighbor==RouterName:#不想自身发送
            continue
        ip=NEIGHBOR_ROUTER.get(neighbor,'')#获取该路由器的IP信息
        if len(ip)<=0:#IP信息获取失败
            print("No ip address of neighbor {}!".format(neighbor))
            continue

        sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        sock.connect((ip,LISTEN_DV_PORT))

        content={'RouterName':RouterName,'dv':DV[RouterName].copy()}
        for k,v in FORWARD_TABLE.items():
            #如果到达路由器k的路径经过该邻居
            #则设置路由器RouterName到路由器k为不可达
            #增加毒性逆转
            if v==neighbor and k!=neighbor:
                content['dv'][k]=INFINITY
        send=json.dumps(content).encode()
        sock.send(send)
        sock.close()
        print('Send to {}'.format(neighbor),content)


def update_dv():#更新本地路由器距离向量
    global DV,FORWARD_TABLE
    changed=False#标志自身路由器的距离向量是否会更新
    forward_port=''
    FORWARD_TABLE={}
    #确定经过本地路由器的邻居k2到达目的路由器k1的最短路径
    for k1 in DV[RouterName].keys():#遍历在本地路由器的距离向量中的所有路由器
        if k1==RouterName:
            continue
        temp=INFINITY
        forward_port=''
        for k2 in neighbor_cost.keys():
            if k2!=RouterName:
                #Bellman-Ford方程
                # temp=min(temp,neighbor_cost[k2]+DV[k2].get(k1,INFINITY))
                sum=neighbor_cost[k2]+DV[k2].get(k1,INFINITY)
                if sum<temp:
                    temp=sum
                    if temp!=INFINITY:
                        forward_port=k2

        if temp!=DV[RouterName][k1]:#到路由器k1的最小代价发送变化
            changed=True
        DV[RouterName][k1]=temp
        if len(forward_port)>0:#设置下一跳的路由器
            FORWARD_TABLE[k1]=forward_port

    if changed:
        send_DV_neighbor()
    print(DV)

class ReadTopo(threading.Thread):#周期性读取本地topo文件查看链路状况是否变化
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global neighbor_cost,DV,FORWARD_TABLE
        while(True):
            f=open(TOPO_FILE,"r")
            f.seek(io.SEEK_SET)
            size=os.path.getsize(TOPO_FILE)
            s=f.read(size)
            j=json.loads(s)
            
            temp_neighbor_cost=j.get(RouterName,{})
            temp_neighbor_cost[RouterName]=0

            # print(temp_neighbor_cost)
            if temp_neighbor_cost!=neighbor_cost:#如果链路状态变化
                time.sleep(20)
                neighbor_cost=temp_neighbor_cost#更新到邻居路由器的代价
                DV[RouterName]=neighbor_cost.copy()#更新距离向量表
                FORWARD_TABLE={}#转发表置空
                send_DV_neighbor()#向邻居发送更新后的距离向量
    
            f.close()
            time.sleep(5)

class ListenDV(threading.Thread):#监听DV交互消息
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global DV
        sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        sock.bind(('',LISTEN_DV_PORT))
        sock.listen()

        while True:
            conn,addr=sock.accept()
            recv=conn.recv(SOCKET_BUFFER_SIZE)
            recv=json.loads(recv.decode())
            
            name=recv['RouterName']
            dv=recv['dv']
            temp=DV.get(name,{})
            if len(temp)==0:
                print("Wrong distance vector, it will be discarded.")
                continue
            print("received: ",recv)
            DV[name]=dv.copy()
            for k in dv.keys():
                DV[name]=dv#更新距离向量表中路由器name的表项
                if not k in DV[RouterName].keys():#如果路由器k不是本地路由器的邻居
                    DV[RouterName][k]=INFINITY#将其加入到本地路由器的距离向量中，并设置代价为正无穷
            update_dv()#更新距离向量表
            conn.close()

        sock.close()

class ListenMessage(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        sock.bind(('',LISTEN_MG_PORT))
        sock.listen()

        while True:
            conn,addr=sock.accept()
            recv=conn.recv(SOCKET_BUFFER_SIZE)
            recv=json.loads(recv.decode())

            print('receive a message',recv)

            if recv['Dst']==RouterName:#消息的目的地正是本地路由器
                print('Message from {}:'.format(recv['Src']))#直接展示消息
                print(recv['Message'])
            
            else:#转发消息
                next_hop=FORWARD_TABLE.get(recv['Dst'],'')#从转发表中找到下一跳路由器
                if len(next_hop)<=0:
                    print('No bext hop router,ERROR!!!')
                    return False
                ip=NEIGHBOR_ROUTER.get(next_hop,'')#找到下一跳路由器的IP
                if len(ip)<=0:
                    print("No ip address of {}".format(next_hop))
                
                send_sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                send_sock.connect((ip,LISTEN_MG_PORT))
                send=json.dumps(recv)
                send_sock.send(send.encode())
                send_sock.close()
        sock.close()

class SendMessage(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    
    def run(self):
        while True:
            print('Please select a router:')
            for router in FORWARD_TABLE.keys():
                if router==RouterName:
                    continue
                print("{}".format(router),end='')
                print()
            
            while True:
                router=input()
                if not router in FORWARD_TABLE.keys():
                    print("No such router, please try another router")
                else:
                    break
            
            message=input('Please input the message you want to send:')
            sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
            next_hop=FORWARD_TABLE.get(router,'')#发送给下一跳路由器
            if len(next_hop)<=0:
                print('No next hop router,ERROR!!!')
                return False
            
            cost=DV[RouterName].get(router,INFINITY)
            
            if cost>=INFINITY:
                print("Can't send message to ",router)
                continue

            ip=NEIGHBOR_ROUTER.get(next_hop,'')
            if len(ip)<=0:
                print("No ip address of {}".format(next_hop))
            
            sock.connect((ip,LISTEN_MG_PORT))
            send={'Src':RouterName,'Dst':router,'Message':message}
            send=json.dumps(send).encode()
            sock.send(send)
            sock.close()
            print('send succeeded!')

if __name__ == "__main__":#主函数
    init()
    thread2=ListenDV()
    thread2.start()

    thread4=ListenMessage()
    thread4.start()

    time.sleep(30)
    send_DV_neighbor()

    thread1=ReadTopo()
    thread1.start()

    thread3=SendMessage()
    thread3.start()